/*
 * Long term port to Java.
 * Copyright (C) 1999  Christopher Edwards
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

package org.tritonus.lowlevel.gsm;

public class Long_term
{
    public void Gsm_Long_Term_Predictor(short[] d, /* [0..39] residual signal IN */
    int k, /* d entry point, which 40 */
    short[] e, /* [0..39] add 5 to index OUT */
    short[] dp, short[] dpp, int dp_dpp_point_dp0, short[] Nc, /*
                                                                * correlation
                                                                * lag OUT
                                                                */
    short[] bc, /* gain factor OUT */
    int Nc_bc_index)
    {
        Calculation_of_the_LTP_parameters(d, k, dp, dp_dpp_point_dp0, bc, Nc,
                Nc_bc_index);
        Long_term_analysis_filtering(bc[Nc_bc_index], Nc[Nc_bc_index], dp, d,
                k, dpp, e, dp_dpp_point_dp0);
    }

    private void Calculation_of_the_LTP_parameters(short[] d, /* [0..39] IN */
    int d_index, short[] dp, /* [-120..-1] IN */
    int dp_start, short[] bc_out, /* OUT */
    short[] Nc_out, /* OUT */
    int Nc_bc_index) throws IllegalArgumentException
    {

        int lambda = 0;
        short Nc = 0;
        short[] wt = new short[40];

        int L_max = 0, L_power = 0;
        short R = 0, S = 0, dmax = 0, scal = 0;
        short temp = 0;

        /*
         * Search of the optimum scaling of d[0..39].
         */
        for (int k = 0; k <= 39; k++)
        {
            temp = d[k + d_index];
            temp = Add.GSM_ABS(temp);
            if (temp > dmax)
            {
                dmax = temp;
            }
        }

        temp = 0;

        if (dmax == 0)
        {
            scal = 0;
        }
        else
        {
            if (!(dmax > 0))
            {
                throw new IllegalArgumentException(
                        "Calculation_of_the_LTP_parameters: dmax = " + dmax
                                + " should be > 0.");
            }
            temp = Add.gsm_norm(dmax << 16);
        }

        if (temp > 6)
        {
            scal = 0;
        }
        else
        {
            scal = (short) (6 - temp);
        }

        if (!(scal >= 0))
        {
            throw new IllegalArgumentException(
                    "Calculation_of_the_LTP_parameters: scal = " + scal
                            + " should be >= 0.");
        }

        /*
         * Initialization of a working array wt
         */

        for (int k = 0; k <= 39; k++)
        {
            wt[k] = Add.SASR(d[k + d_index], scal);
        }

        /*
         * Search for the maximum cross-correlation and coding of the LTP lag
         */
        L_max = 0;
        Nc = 40; /* index for the maximum cross-correlation */

        for (lambda = 40; lambda <= 120; lambda++)
        {
            int L_result = 0;
            int step = 1;

            L_result = STEP(0, wt, dp, dp_start - lambda);
            L_result += STEP(1, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(2, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(3, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(4, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(5, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(6, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(7, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(8, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(9, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(10, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(11, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(12, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(13, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(14, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(15, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(16, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(17, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(18, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(19, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(20, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(21, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(22, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(23, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(24, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(25, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(26, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(27, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(28, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(29, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(30, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(31, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(32, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(33, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(34, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(35, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(36, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(37, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(38, wt, dp, step + dp_start - lambda);
            step++;
            L_result += STEP(39, wt, dp, step + dp_start - lambda);
            step++;

            if (L_result > L_max)
            {
                Nc = (short) lambda;
                L_max = L_result;
            }
        }

        Nc_out[Nc_bc_index] = Nc;

        L_max <<= 1;

        /*
         * Rescaling of L_max
         */
        if (!(scal <= 100 && scal >= -100))
        {
            throw new IllegalArgumentException(
                    "Calculation_of_the_LTP_parameters: scal = " + scal
                            + " should be >= -100 and <= 100.");
        }

        L_max = L_max >> (6 - scal); /* sub(6, scal) */

        if (!(Nc <= 120 && Nc >= 40))
        {
            throw new IllegalArgumentException(
                    "Calculation_of_the_LTP_parameters: Nc = " + Nc
                            + " should be >= 40 and <= 120.");
        }

        /*
         * Compute the power of the reconstructed short term residual signal
         * dp[..]
         */
        L_power = 0;
        for (int k = 0; k <= 39; k++)
        {
            int L_temp;

            L_temp = Add.SASR(dp[k - Nc + dp_start], 3);
            L_power += L_temp * L_temp;
        }
        L_power <<= 1; /* from L_MULT */

        /*
         * Normalization of L_max and L_power
         */

        if (L_max <= 0)
        {
            bc_out[Nc_bc_index] = 0;
            return;
        }
        if (L_max >= L_power)
        {
            bc_out[Nc_bc_index] = 3;
            return;
        }

        temp = Add.gsm_norm(L_power);

        R = Add.SASR(L_max << temp, 16);
        S = Add.SASR(L_power << temp, 16);

        /*
         * Coding of the LTP gain
         */

        /*
         * Table 4.3a must be used to obtain the level DLB[i] for the
         * quantization of the LTP gain b to get the coded version bc.
         */
        for (int bc = 0; bc <= 2; bc++)
        {
            if (R <= Add.GSM_MULT(S, Gsm_Def.gsm_DLB[bc]))
            {
                break;
            }
            bc_out[Nc_bc_index] = (short) bc;
        }
    }

    private int STEP(int k, short[] wt, short[] dp, int dp_i)
    {
        return (wt[k] * dp[dp_i]);
    }

    /*
     * In this part, we have to decode the bc parameter to compute the samples
     * of the estimate dpp[0..39]. The decoding of bc needs the use of table
     * 4.3b. The long term residual signal e[0..39] is then calculated to be fed
     * to the RPE encoding section.
     */
    static void Long_term_analysis_filtering(short bc, /* IN */
    short Nc, /* IN */
    short[] dp, /* previous d [-120..-1] IN */
    short[] d, /* d [0..39] IN */
    int d_index, short[] dpp, /* estimate [0..39] OUT */
    short[] e, /* long term res. signal [0..39] OUT */
    int dp_dpp_index)
    {
        short BP = 0;

        switch (bc)
        {
        case 0:
            BP = (short) 3277;
            for (int k = 0; k <= 39; k++)
            {
                dpp[k + dp_dpp_index] = Add.GSM_MULT_R(BP, dp[k - Nc
                        + dp_dpp_index]);
                e[k + 5] = Add.GSM_SUB(d[k + d_index], dpp[k + dp_dpp_index]);
            }
            break;

        case 1:
            BP = (short) 11469;
            for (int k = 0; k <= 39; k++)
            {
                dpp[k + dp_dpp_index] = Add.GSM_MULT_R(BP, dp[k - Nc
                        + dp_dpp_index]);
                e[k + 5] = Add.GSM_SUB(d[k + d_index], dpp[k + dp_dpp_index]);
            }
            break;

        case 2:
            BP = (short) 21299;
            for (int k = 0; k <= 39; k++)
            {
                dpp[k + dp_dpp_index] = Add.GSM_MULT_R(BP, dp[k - Nc
                        + dp_dpp_index]);
                e[k + 5] = Add.GSM_SUB(d[k + d_index], dpp[k + dp_dpp_index]);
            }
            break;

        case 3:
            BP = (short) 32767;
            for (int k = 0; k <= 39; k++)
            {
                dpp[k + dp_dpp_index] = Add.GSM_MULT_R(BP, dp[k - Nc
                        + dp_dpp_index]);
                e[k + 5] = Add.GSM_SUB(d[k + d_index], dpp[k + dp_dpp_index]);
            }
            break;
        }
    }

    /*
     * This procedure uses the bcr and Ncr parameter to realize the long term
     * synthesis filtering. The decoding of bcr needs table 4.3b.
     */
    public void Gsm_Long_Term_Synthesis_Filtering(Gsm_State S, short Ncr,
            short bcr, short[] erp, /* [0..39] IN */
            int dp0_index_start_drp
    /* [-120..-1] IN, [0..40] OUT */
    /*
     * drp is a pointer into the Gsm_State dp0 short array.
     */
    ) throws IllegalArgumentException
    {
        short brp, drpp, Nr;
        short[] drp = S.getDp0();

        /*
         * Check the limits of Nr.
         */
        Nr = Ncr < 40 || Ncr > 120 ? S.getNrp() : Ncr;

        S.setNrp(Nr);

        if (!(Nr >= 40 && Nr <= 120))
        {
            throw new IllegalArgumentException(
                    "Gsm_Long_Term_Synthesis_Filtering Nr = " + Nr
                            + " is out of range. Should be >= 40 and <= 120");
        }

        /*
         * Decoding of the LTP gain bcr
         */
        brp = Gsm_Def.gsm_QLB[bcr];

        /*
         * Computation of the reconstructed short term residual signal
         * drp[0..39]
         */
        if (brp == Gsm_Def.MIN_WORD)
        {
            throw new IllegalArgumentException(
                    "Gsm_Long_Term_Synthesis_Filtering brp = " + brp
                            + " is out of range. Should be = "
                            + Gsm_Def.MIN_WORD);
        }

        for (int k = 0; k <= 39; k++)
        {
            drpp = Add.GSM_MULT_R(brp, drp[k - Nr + dp0_index_start_drp]);
            drp[k + dp0_index_start_drp] = Add.GSM_ADD(erp[k], drpp);
        }

        /*
         * Update of the reconstructed short term residual signal
         * 
         * drp[ -1..-120 ]
         */
        System.arraycopy(drp, (dp0_index_start_drp - 80), drp,
                (dp0_index_start_drp - 120), 120);

        S.setDp0(drp);
    }
}
